/*
 * q_empath.c		EmPath.
 *
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 *
 * Authors:	Jaroslaw Sliwinski <jarek.sliwinski@gmail.com>
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>

#include "utils.h"
#include "tc_util.h"
#include "tc_common.h"

#include "pkt_sched_empath.h"

static void explain(void)
{
	fprintf(stderr,
"Usage: ... empath [ limit PACKETS ] \n" \
"                  [ timeunit USECS ]\n" \
"                  [ timescope USECS ]\n" \
"                  [ distribution <file> ]\n");

}

static void explain1(const char *arg)
{
	fprintf(stderr, "Illegal \"%s\"\n", arg);
}

#define usage() return(-1)

/*
 * Simplistic file parser for distrbution data.
 * Format is:
 *	# numer_of_tables
 *      10
 *      # START table 0
 *      # description: number_of_entries ploss(/0xffffffff)
 *      100 1000
 *      # entries: x_usec y_dist(/0xffffffff)
 *	1000 1000000
 *      2000 1000000
 *      ...
 *      # FINISH table 0
 *      ...
 */
static struct tc_empath_parameters *get_distribution(const char *filename, size_t *size)
{
	FILE *f;
	size_t len;
	char *line = NULL;
	unsigned int dist_count = 0;
	unsigned int entries_count = 0;
	unsigned int ploss = 0;
	unsigned int x, y, next_table;
	int ret, i, j, total_size;
	char *p, *endp;
	struct tc_empath_dist **dists, *dist_ptr;
	struct tc_empath_parameters *params;
	*size = 0;
	if ((f = fopen(filename, "r")) == NULL) {
		fprintf(stderr, "Unable to read file %s (%s)\n",
			filename, strerror(errno));
		return NULL;
	}
	/* read distribution table count */
	while ( (ret=getline(&line, &len, f)) != -1) {
		if (*line == '\n' || *line == '#')
			continue;
		p = line;
		dist_count = strtoul(p, &endp, 0);
		if (p == endp)
		{
			fprintf(stderr, "Missing distribution count\n");
			goto error;
		}
		p = endp;
		break;
	}
	if (ret < 0)
		goto error;
	if (dist_count == 0)
	{
		fprintf(stderr,
			"Invalid value of distribution count %d\n",
			dist_count);
		goto error;
	}
	dists = malloc(dist_count*sizeof(struct tc_empath_dist *));
	if (!dists)
	{
		fprintf(stderr, 
			"Unable to allocate memory for distribution tables\n");
		goto error;
	}
	memset(dists, 0, dist_count*sizeof(struct tc_empath_dist *));
	/* read each distribution */
	for (i = 0; i < dist_count; i++)
	{
		/* number of entries and packet loss */
		while ( (ret=getline(&line, &len, f)) != -1) {
			if (*line == '\n' || *line == '#')
				continue;
			p = line;
			entries_count = strtoul(p, &endp, 0);
			if (p == endp)
			{
				fprintf(stderr, 
					"Missing entry count for distribution %d\n",
					i);
				goto error;
			}
			p = endp;
			ploss = strtoul(p, &endp, 0);
			if (p == endp)
			{
				fprintf(stderr,
					"Missing ploss for distribution %d\n",
					i);
				goto error;
			}
			break;
		}
		if (ret < 0)
			goto error;
		if (entries_count == 0)
		{
			fprintf(stderr, 
				"Invalid value of entry count %d for distribution %d\n",
				entries_count, i);
			goto error;
		}
		dists[i] = malloc(sizeof(struct tc_empath_dist) + 
				  entries_count*sizeof(struct tc_empath_dist_entry));
		if (!dists[i])
		{
			fprintf(stderr, 
				"Unable to allocate memory for distribution %d\n",
				i);
			goto error;
		}
		dists[i]->packet_loss = ploss;
		dists[i]->entries_count = entries_count;
		/* read entries */
		for (j = 0; j < entries_count; j++)
		{
			while ( (ret=getline(&line, &len, f)) != -1) {
				if (*line == '\n' || *line == '#')
					continue;
				p = line;
				x = strtoul(p, &endp, 0);
				if (p == endp)
				{
					fprintf(stderr,
						"Missing x for distribution %d entry %d\n",
						i, j);
					goto error;
				}
				p = endp;
				y = strtoul(p, &endp, 0);
				if (p == endp)
				{
					fprintf(stderr,
						"Missing y for distribution %d entry %d\n",
						i, j);
					goto error;
				}
				p = endp;
				next_table = strtoul(p, &endp, 0);
				if (p == endp)
				{
					fprintf(stderr,
						"Missing next_table "\
						"for distribution %d entry %d\n",
						i, j);
					goto error;
				}
				if (next_table >= dist_count)
				{
					fprintf(stderr,
						"Invalid next table %d "\
						"for distribution %d entry %d\n",
						next_table, i, j);
					goto error;
				}
				dists[i]->entries[j].x = x;
				dists[i]->entries[j].y_high = y;
				dists[i]->entries[j].next_table = next_table;
				break;
			}
			if (ret < 0)
			{
				fprintf(stderr,
					"End of entries for distribution %d at entry %d\n",
					i, j);
				goto error;
			}
		}
		/* entries are completed - go to next table */
	}
	/* tables are completed - prepare the NLA message */
	total_size = sizeof(struct tc_empath_parameters);
	for (i = 0; i < dist_count; i++)
	{
		total_size += sizeof(struct tc_empath_dist);
		total_size += 
			dists[i]->entries_count*sizeof(struct tc_empath_dist_entry);
	}
	printf("Total message size %d\n", total_size);
	if (total_size >= TCA_BUF_MAX)
	{
		fprintf(stderr,
			"Message too large %d with limit %d\n",
			total_size, TCA_BUF_MAX);
		goto error;
	}
	params = malloc(total_size);
	if (!params)
	{
		fprintf(stderr,
			"Unable allocate memory for message\n");
		goto error;
	}
	params->dist_count = dist_count;
	params->queue_limit = 1000;
	params->interarrival = 0;
	dist_ptr = &params->dists[0];
	for (i = 0; i < dist_count; i++)
	{
		j = sizeof(struct tc_empath_dist);
		j += dists[i]->entries_count*sizeof(struct tc_empath_dist_entry);
		memcpy(dist_ptr, dists[i], j);
		dist_ptr = (struct tc_empath_dist *)(((char *)dist_ptr) + j);
	}
	*size = total_size;
	free(line);
	fclose(f);
	return params;
 error:
	if (dists)
	{
		for (i=0; i < dist_count; i++)
			if (dists[i]) free(dists[i]);
		free(dists);
	}
	free(line);
	fclose(f);
	return NULL;
}

static int empath_parse_opt(struct qdisc_util *qu, int argc, char **argv,
			   struct nlmsghdr *n)
{
	size_t message_size = 0;
	unsigned int queue_limit = 1000;
	unsigned int interarrival = 0;
	unsigned int scope = 0;
	struct rtattr *tail;
	struct tc_empath_parameters *params = NULL;

	while (argc > 0) {
		if (matches(*argv, "limit") == 0) {
			NEXT_ARG();
			if (get_size(&queue_limit, *argv)) {
				explain1("limit");
				return -1;
			}
		} else if (matches(*argv, "timeunit") == 0) {
			NEXT_ARG();
			if (get_size(&interarrival, *argv)) {
				explain1("timeunit");
				return -1;
			}
		} else if (matches(*argv, "timescope") == 0) {
			NEXT_ARG();
			if (get_size(&scope, *argv)) {
				explain1("timescope");
				return -1;
			}
		} else if (matches(*argv, "distribution") == 0) {
			NEXT_ARG();
			params = get_distribution(*argv, &message_size);
			if (!params) {
				return -1;
			}
		} else if (strcmp(*argv, "help") == 0) {
			explain();
			return -1;
		} else {
			fprintf(stderr, "What is \"%s\"?\n", *argv);
			explain();
			return -1;
		}
		argc--; argv++;
	}
	if (! params)
	{
		fprintf(stderr, "Please provide the distribution\n");
		explain();
		return -1;
	}
	if (interarrival == 0)
	{
		fprintf(stderr, "Please provide timeunit\n");
		free(params);
		return -1;
	}
	if (interarrival < 100)
	{
		fprintf(stderr, "Timeunit should be greater than 100 usec\n");
		free(params);
		return -1;
	}
	if (scope == 0)
	{
		fprintf(stderr, "Please provide timescope\n");
		free(params);
		return -1;
	}
	if (scope < interarrival)
	{
		fprintf(stderr, "Increasing scope to %d\n", interarrival);
		scope = interarrival;
	}
	if (queue_limit == 0)
	{
		fprintf(stderr, "Increasing queue limit to %d\n", 10);
		queue_limit = 10;
	}
	params->queue_limit = queue_limit;
	params->interarrival = interarrival;
	params->interarrival_scope = scope;

	tail = NLMSG_TAIL(n);
	addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
	if (addattr_l(n, TCA_BUF_MAX, TCA_EMPATH_PARAMETERS, params, message_size) < 0)
	{
		free(params);
		return -1;
	}
	free(params);
	
	tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
	printf("message complete\n");
	return 0;
}

static int empath_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
{
	fprintf(f, "Unsupported (yet)\n");

	return 0;
}

struct qdisc_util empath_qdisc_util = {
	.id	   	= "empath",
	.parse_qopt	= empath_parse_opt,
	.print_qopt	= empath_print_opt,
};

